package com.pingan.haofang.searchcloud.index.service.impl;

import com.google.gson.Gson;
import com.pingan.haofang.framework.common.functional.lang.control.base.Enums;
import com.pingan.haofang.searchcloud.api.constants.EngineType;
import com.pingan.haofang.searchcloud.api.exception.SearchException;
import com.pingan.haofang.searchcloud.api.matedata.FieldMeta;
import com.pingan.haofang.searchcloud.api.matedata.IndexMeta;
import com.pingan.haofang.searchcloud.api.matedata.IndexMetadataHolderFactory;
import com.pingan.haofang.searchcloud.api.query.parser.SelectStatementParser;
import com.pingan.haofang.searchcloud.api.query.parser.element.SelectStatement;
import com.pingan.haofang.searchcloud.api.route.IndexRoute;
import com.pingan.haofang.searchcloud.api.route.IndexRouteRegisterFactory;
import com.pingan.haofang.searchcloud.common.configuration.ClusterProperties;
import com.pingan.haofang.searchcloud.es.api.ElasticSearchSearchService;
import com.pingan.haofang.searchcloud.es.api.SearchResultDTO;
import com.pingan.haofang.searchcloud.index.dto.ColumnModel;
import com.pingan.haofang.searchcloud.index.dto.NativeResultDTO;
import com.pingan.haofang.searchcloud.index.dto.SqlExplainDTO;
import com.pingan.haofang.searchcloud.index.dto.SqlResultDTO;
import com.pingan.haofang.searchcloud.index.service.ClusterService;
import com.pingan.haofang.searchcloud.index.service.SearchService;
import com.pingan.haofang.searchcloud.solr.api.SolrQueryBuilder;
import com.pingan.haofang.searchcloud.solr.api.SolrQueryResult;
import com.pingan.haofang.searchcloud.solr.api.SolrQueryService;
import com.pingan.haofang.searchcloud.solr.api.SolrQueryStatement;
import net.sf.jsqlparser.expression.Alias;
import net.sf.jsqlparser.expression.Function;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.statement.select.AllColumns;
import net.sf.jsqlparser.statement.select.AllTableColumns;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.select.SelectExpressionItem;
import net.sf.jsqlparser.statement.select.SelectItem;
import org.apache.solr.client.solrj.SolrQuery;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

@Component
public class SearchServiceImpl implements SearchService {

    static Logger LOG = LoggerFactory.getLogger(SearchServiceImpl.class);

    static Logger SQL_LOG = LoggerFactory.getLogger("SQL_LOG");

    private Gson GSON = new Gson();

    @Autowired
    private SolrQueryService solrQueryService;

    @Autowired
    private ElasticSearchSearchService elasticSearchSearchService;

    @Autowired
    ClusterService clusterService;

    @Override
    public SqlResultDTO query(String sql) {
        LOG.info("search query, sql: [{}]", sql);
        String indexName = "";
        long start = System.currentTimeMillis();

        try {
            // SQL解析
            SelectStatement statement = SelectStatementParser.parse(sql, IndexMetadataHolderFactory.get());

            IndexMeta meta = statement.getMeta();
            indexName = meta.getIndexName();

            return doQuery(sql, statement, meta);
        } finally {
            long time = System.currentTimeMillis() - start;
            SQL_LOG.info("{} | {} | {}", indexName, sql, time);
        }
    }


    /**
     * solr原生查询
     *
     * @param indexName
     * @param params
     * @return
     */
    @Override
    public NativeResultDTO nativeQuery(String indexName, Map<String, String> params) {
        long start = System.currentTimeMillis();
        try {
            return doNativeQuery(indexName, params);
        } finally {
            long time = System.currentTimeMillis() - start;
            SQL_LOG.info("{} | {} | {}", indexName, GSON.toJson(params), time);
        }


    }

    private NativeResultDTO doNativeQuery(String indexName, Map<String, String> params) {
        IndexMeta meta = IndexMetadataHolderFactory.get().findIndexMeta(indexName);
        EngineType engineType = Enums.findValue(meta.getEngineType(), EngineType.class);
        switch (engineType) {
            case SOLR:
                String collection = IndexRouteRegisterFactory.get().discover(meta.getIndexName()).getCollectionName();
                SolrQuery solrQuery = new SolrQuery();
                for (Map.Entry<String, String> entry : params.entrySet()) {
                    solrQuery.set(entry.getKey(), entry.getValue());
                }

                SolrQueryResult queryResult = solrQueryService.query(solrQuery, collection, meta.getHost());
                NativeResultDTO dto = new NativeResultDTO();
                dto.setResult(queryResult.getResult());
                dto.setResultSize(queryResult.getResultSize());
                dto.setTime(queryResult.getTime());
                dto.setTotalFound(queryResult.getTotalFound());
                dto.setHighlighting(queryResult.getHighlighting());
                return dto;
            default:
                throw new SearchException("unsupported engine type [" + meta.getEngineType() + "], please check");
        }
    }

    @Override
    public NativeResultDTO esNativeQuery(String indexName, String params) throws Exception {
        long start = System.currentTimeMillis();
        try {
            IndexMeta meta = IndexMetadataHolderFactory.get().findIndexMeta(indexName);
            String collection = IndexRouteRegisterFactory.get().discover(meta.getIndexName()).getCollectionName();

            NativeResultDTO nativeResultDTO = new NativeResultDTO();
            SearchResultDTO resultDTO = elasticSearchSearchService.query(collection, params, meta.getHost());
            nativeResultDTO.setResult(resultDTO.getResult());
            nativeResultDTO.setResultSize(resultDTO.getResultSize());
            nativeResultDTO.setTotalFound(resultDTO.getTotalFound());
            nativeResultDTO.setTime(resultDTO.getTime());

            return nativeResultDTO;
        } finally {
            long time = System.currentTimeMillis() - start;
            SQL_LOG.info("{} | {} | {}", indexName, GSON.toJson(params), time);
        }

    }


    private SqlResultDTO doQuery(String sql, SelectStatement statement, IndexMeta meta) {
        // 执行查询
        EngineType engineType = Enums.findValue(meta.getEngineType(), EngineType.class);

        Map<String, String> resultColumnTypes = meta.getFiledMetas().entrySet().stream().map(Map.Entry::getValue)
                .collect(Collectors.toMap(FieldMeta::getName, m -> m.getType().getFieldType(),
                        (a, b) -> a, LinkedHashMap::new));


        switch (engineType) {
            case SOLR:
                String collection =
                        IndexRouteRegisterFactory.get().discover(meta.getIndexName()).getCollectionName();
                SolrQueryResult queryResult = solrQueryService.query(statement, collection, meta.getHost());
                SqlResultDTO dto = new SqlResultDTO();
                dto.setResult(queryResult.getResult());
                dto.setResultSize(queryResult.getResultSize());
                dto.setTime(queryResult.getTime());
                dto.setTotalFound(queryResult.getTotalFound());

                List<ColumnModel> solrColumnProperties = obtainColumnProperties(statement.getSelect(),
                        resultColumnTypes);
                dto.setColumnProperties(solrColumnProperties);
                return dto;
            case ES:
                try {
                    dto = new SqlResultDTO();
                    SearchResultDTO searchResultDTO = elasticSearchSearchService.query(meta.getHost(), sql, statement);
                    dto.setResult(searchResultDTO.getResult());
                    dto.setTime(searchResultDTO.getTime());
                    dto.setResultColumns(searchResultDTO.getResultColumns());
                    dto.setTotalFound(searchResultDTO.getTotalFound());
                    dto.setResultSize(searchResultDTO.getResultSize());

                    List<ColumnModel> esColumnProperties = obtainColumnProperties(statement.getSelect(),
                            resultColumnTypes);
                    dto.setColumnProperties(esColumnProperties);
                    return dto;
                } catch (Exception e) {
                    LOG.info(e.getMessage(), e);
                    throw new RuntimeException(e);
                }
            default:
                throw new SearchException("unsupported engine type [" + meta.getEngineType() + "], please check");
        }
    }

    @Override
    public SqlExplainDTO explain(String sql) {

        // SQL解析
        SelectStatement statement = SelectStatementParser.parse(sql, IndexMetadataHolderFactory.get());

        IndexMeta meta = statement.getMeta();

        SqlExplainDTO dto = new SqlExplainDTO();

        // 执行查询
        EngineType engineType = Enums.findValue(meta.getEngineType(), EngineType.class);
        switch (engineType) {
            case SOLR:
                String collection =
                        IndexRouteRegisterFactory.get().discover(meta.getIndexName()).getCollectionName();
                SolrQueryStatement solrStatement = SolrQueryBuilder.build(statement);
                dto.setCollection(collection);
                dto.setDialectQuery(solrStatement.getSolrQuery().toString());
                dto.setIndexName(meta.getIndexName());
                dto.setSql(sql);
                break;
            case ES:
                collection = IndexRouteRegisterFactory.get().discover(meta.getIndexName()).getCollectionName();
                dto.setCollection(collection);
                dto.setDialectQuery(elasticSearchSearchService.explain(meta.getHost(), sql, statement));
                dto.setIndexName(meta.getIndexName());
                dto.setSql(sql);
                break;
            default:
                throw new SearchException("unsupported engine type [" + meta.getEngineType() + "], please check");
        }

        return dto;
    }


    /**
     * 获取列属性
     *
     * @param selectStatement
     * @param resultColumnTypes
     * @return
     */
    public List<ColumnModel> obtainColumnProperties(Select selectStatement, Map<String,
            String> resultColumnTypes) {
        List<ColumnModel> columnProperties = new ArrayList<>();

        //是否有*,t.*
        boolean hasAllColumns = false;


        PlainSelect plainSelect = (PlainSelect) selectStatement.getSelectBody();

        for (SelectItem selectItem : plainSelect.getSelectItems()) {

            if (selectItem instanceof AllColumns || selectItem instanceof AllTableColumns) {
                hasAllColumns = true;
                continue;
            }

            SelectExpressionItem selectExpressionItem = (SelectExpressionItem) selectItem;

            //字段名
            final String columnName;

            //是否有别名
            final boolean hasAlias;

            //是否函数
            final boolean isFunction;
            //函数名称
            final String functionName;

            //solrType
            final String solrType;

            //列
            if (selectExpressionItem.getExpression() instanceof Column) {
                columnName = ((Column) selectExpressionItem.getExpression()).getColumnName();
                isFunction = false;
                functionName = null;

                solrType = resultColumnTypes.get(columnName);

                //函数
            } else if (selectExpressionItem.getExpression() instanceof Function) {
                columnName = selectExpressionItem.getExpression().toString();
                isFunction = true;
                functionName = ((Function) selectExpressionItem.getExpression()).getName();

                solrType = null;

                //暂时不支持其他类型
            } else {
                throw new UnsupportedOperationException("not support column : " + selectExpressionItem);
            }


            //别名
            String alias = Optional.ofNullable(selectExpressionItem)
                    .map(SelectExpressionItem::getAlias).map(Alias::getName).orElse(null);

            //是否有别名
            hasAlias = alias == null ? false : true;

            //最终列名
            String finalColumnName = alias == null ? columnName : alias;

            columnProperties.add(ColumnModel.newBuilder().originColumnName(columnName).hasALias(hasAlias)
                    .finalColumnName(finalColumnName).solrType(solrType).asFunction(isFunction).functionName(functionName).build());

        }

        //如果有 *,t.*则根据columns返回的列名解析这些字段
        if (hasAllColumns) {
            resultColumnTypes.forEach(
                    (columnName, solrType) -> {
                        columnProperties.add(ColumnModel.newBuilder().originColumnName(columnName).hasALias(false)
                                .finalColumnName(columnName).solrType(solrType).asFunction(false).functionName(null).build());
                    }
            );
        }
        return columnProperties;
    }

}
